package ru.averta.notes.web.api;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import ru.averta.notes.domain.CoolMark;
import ru.averta.notes.domain.Site;
import ru.averta.notes.domain.Tag;
import ru.averta.notes.domain.ajax.*;
import ru.averta.notes.service.CoolMarkService;
import ru.averta.notes.service.SiteService;
import ru.averta.notes.service.UrlService;
import ru.averta.notes.service.security.SecurityService;
import ru.averta.notes.service.security.token.ApiTokenBasedRememberMeServices;
import ru.averta.notes.service.tag.TagManager;
import ru.averta.notes.service.tag.TagsSuggestService;
import ru.averta.notes.service.tag.TagsSuggestServiceImpl;

import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * <code>ApiController</code>
 *
 * @author fedor.belov (fedor.belov@mail.ru)
 *         Date: 02.09.11 15:58
 */

@Controller
public class ApiController {

    private static final Logger logger = LoggerFactory.getLogger(ApiController.class);

    @Autowired private SecurityService security;
    @Autowired private SiteService siteService;
    @Autowired private TagManager tm;
    @Autowired private CoolMarkService markService;
    @Autowired private UrlService urlService;

    @Autowired
    @Qualifier("GoogleSuggestServiceImpl")
    private TagsSuggestService tagsSuggestor;

    /**
     * Возвращает токен после удачной попытки логина
     *
     * @return
     */
    @RequestMapping(value = "/api/login")
    public @ResponseBody String loginOk(HttpServletRequest request) {
        return (String) request.getAttribute(ApiTokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
    }

    /**
     * Запрос на добавление URL'а в систему без контента
     * Только Url и, возможно, заголовок
     *
     * @param content
     * @return
     */
    @RequestMapping(value = "/api/add")
    public @ResponseBody String addUrl(@RequestBody AjaxUrlAddRequest content) {

        //Проверяем заполнение необходимых параметров
        checkUrlParam(content.getUrl());

        //Добавляем
        logger.debug("API: Добавляем url {} в систему", content.getUrl());

        createSiteIfDoesntExist(content.getUrl(), content.getTitle(), false);

        return "ok";
    }

    /**
     * Создает в системе сайт с Url'ом, если таковой еще не существует
     *
     * @param url
     * @param title
     * @param shouldExist
     * @return
     */
    private Site createSiteIfDoesntExist(String url, String title, boolean shouldExist) {
        Site site = siteService.findSiteByUrl(url);
        if (site == null) {
            if (shouldExist) logger.error("API: создаем сайт {}, хотя он уже должен существовать", url);
            site = new Site(urlService.findOrCreateUrl(url), title, security.getCurrentUser());
            siteService.save(site);
        } else {
           if (!shouldExist) logger.error("API: запрос на добавление уже существующего сайта - {}", url);
        }
        return site;
    }

    @RequestMapping(value = "/api/set")
    public @ResponseBody String edit(@RequestBody AjaxUrlEditInfoRequest content) {

        //Проверяем заполнение необходимых параметров
        checkUrlParam(content.getUrl());

        //Редактируем информацию по объекту
        logger.debug("API: Запрос на изменение информации по URL {}", content);

        Site site = createSiteIfDoesntExist(content.getUrl(), content.getTitle(), true);
        if (!site.getTitle().equals(content.getTitle())) {
            //Возможно изменился заголовок (если сайт не был создан только что)
            site.setTitle(content.getTitle());
            siteService.save(site);
        }

        edit_updateTags(site, content.getTags());
        edit_updateMarks(site, content.getMarks());

        return "ok";
    }

    /**
     * Обновляет информацию по тегам - удаляет старые, добавляет новые
     *
     * @param site
     * @param tags
     */
    private void edit_updateTags(Site site, List<String> tags) {
        //Проверяем, возможно какие-то теги были удалены
        Collection<Tag> currentTags = tm.getTagsForObject(site);
        for (Tag tag : currentTags) if (!tags.contains(tag.getTitle())) tm.removeTag(site, tag);

        //Добавляем новые теги
        for (String tag : tags) if (!tm.hasTag(site, tag)) tm.addTag(site, tag);
    }

    /**
     * Обновляет информацию по оценкам / комментариям
     *
     * @param site
     * @param marks
     */
    private void edit_updateMarks(Site site, Set<AjaxCoolMark> marks) {
        //Проще удалить все оценки и сохранить новые
        markService.removeAllMarksForSite(site);

        //Добавляем новые оценки / комментарии
        for (AjaxCoolMark mc : marks) {
            CoolMark mark = new CoolMark(site, mc.getTop(), mc.getLeft(), mc.getContent(), mc.getComment());
            markService.save(mark);
        }
    }

    /**
     * Возвращает данные по URL'у
     * @param url
     * @return
     */
    @RequestMapping(value = "/api/get")
    public @ResponseBody AjaxUrlInfoAnswer getUrlInfo(@RequestParam(required = false, value = "url") String url) {

        //Проверяем заполнение необходимых параметров
        checkUrlParam(url);

        logger.debug("API: Возвращаем информацию по URL'у {}", url);

        //Получаем информацию по URL'у
        Site site = siteService.findSiteByUrl(url);
        if (site == null) {
            return AjaxUrlInfoAnswer.getNotFoundInstance(url);
        } else {
            return AjaxUrlInfoAnswer.getOkInstance(url, site.getTitle(), tm.getTagsForObject(site), site.getMarks());
        }

        /*if ("http://ya.ru".equalsIgnoreCase(url)) {
            AjaxTagContentListAnswer tags = new AjaxTagContentListAnswer();
            tags.addTag("search");
            tags.addTag("поиск");
            Set<AjaxCoolMark> marks = new HashSet<AjaxCoolMark>();
            marks.add(new AjaxCoolMark(15, 90, "это контент", "это комментарий"));
            marks.add(new AjaxCoolMark(15, 639, "что-то другое выделил", "другой коммент"));
            return new AjaxUrlEditInfoRequest("url-1", "title-1", tags, marks);
            //return new AjaxUrlInfoAnswer(url, true, "Яндекс - поисковик", tags, marks);
        } else {
            //return new AjaxUrlInfoAnswer(url, false, null, null, null);
            return null;
        }*/
    }



    /**
     * Возвращает tags suggest с учетом ошибок и опечаток
     *
     * Логика работы:
     * 1. Отбираются все теги
     * 2. Отбираем совпадающие / похожие
     * 3. Смотрим комбинации
     *
     * @param request
     * @return
     */
    @RequestMapping(value = "/api/suggestTags")
    public @ResponseBody AjaxTagsSuggestAnswer tagsSuggest(@RequestBody AjaxTagsSuggestRequest request) {
        return new AjaxTagsSuggestAnswer(tagsSuggestor.suggestTags(request.getPrefix(), request.getCurrentTags()));
    }

    /**
     * Оборачивает процесс создания URL'а, чтобы тот не генерировал Exception
     *
     * @param url
     * @return
     */
    private URL getUrl(String url) {
        try {
            return new URL(url);

        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("url has wrong format - " + url);
        }
    }

    /**
     * Проверяет на заполнение параметра url. Если url не задан - выбрасывает exception
     *
     * @param url
     */
    private void checkUrlParam(String url) {
        if (url == null) {
            throw new IllegalArgumentException("url is required");
        } else {
            try {
                URL urlValue = new URL(url);
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException("url has wrong format");
            }
        }
    }

    /**
     * Обрабатываем exception с неверными параметрами
     * Возвращаем HTTP status code 400 (bad request).
     *
     * @param
     * @return
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleClientErrors(Exception ex) {
        return ex.getMessage();
    }

}
